#include "Texture.h"
#include <lodepng\lodepng.h>

namespace GeorgeLib
{

	static	const	float	default_texcoords[] = { 0, 1, 0, 0, 1, 0, 1, 1 };
	static	const	vec2	default_texcoordsv[] = { vec2(0, 1), vec2(0, 0), vec2(1, 0), vec2(1, 1) };

	Texture::Texture(void)
	{
	}

	Texture::~Texture(void)
	{
		//drop();
	}

	void Texture::drop(void)
	{
		//	DELETE_ARR(imageData);
	}

	bool Texture::LoadTGA(char *filename, bool mipmap)
	{
		GLubyte	TGAheader[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 };	// Uncompressed TGA header
		GLubyte	TGAcompare[12];									// Used to compare TGA header
		GLubyte	header[6];										// First 6 useful bytes from the header
		GLuint	bytesPerPixel;									// Holds number of bytes per pixel used in the .tga file
		GLuint	imageSize;										// Used to store the image size when setting aside memory space
		GLuint	type = GL_RGBA;									// Set the default GL mode to RGBA (32 bits/pixel)

		fstream file;
		file.open(filename, ios::in | ios::binary);

		if (file.is_open())										// Does the file even exist?
		{
			*Logger << "Texture\t\t: LoadTGA - File '" << filename << "' not found\n";
			return false;
		}

		try
		{

			file.read((char*)TGAcompare, sizeof(TGAcompare));			// Try to read the first 12 bytes
			if (file.gcount() != 12) 									// Are the 12 bytes read?
				throw "Reading of header failed";


			if (memcmp(TGAheader, TGAcompare, sizeof(TGAheader)) != 0)	// Does the header match what we want?
				throw "Invalid header";

			file.read((char*)header, sizeof(header));					// Try to read the next 6 bytes
			if (file.gcount() != 6) 										// Are the 6 bytes read?
				throw "Reading of header failed";

			width = header[1] * 256 + header[0];						// Determine the image width	(highbyte*256+lowbyte)
			height = header[3] * 256 + header[2];						// Determine the image height	(highbyte*256+lowbyte)

			if (width <= 0 ||											// Is the width less than or equal to 0?
				height <= 0 ||											// Is the height less than or equal to 0?
				(header[4] != 24 && header[4] != 32)) 					// Is the image 24 or 32 bits?
				throw "Image is not 24 or 32 bits";

			bpp = header[4];								// Grab the image's bits/pixel (24 or 32)
			bytesPerPixel = bpp / 8;									// Divide by 8 to get bytes/pixel
			imageSize = width * height * bytesPerPixel;			// Calculate memory required for image data

			imageData = new GLubyte[imageSize];						// Reserve memory to hold image data

			if (imageData == NULL) 									// Does the storage memory exist?
				throw "Allocation of memory for image data failed";

			file.read((char*)imageData, imageSize);

			if (file.gcount() != (int)imageSize)								// Does the data read match the memory reserved?
			{
				DELETE_ARR(imageData);
				throw "Reading of image data failed";
			}

			for (GLuint i = 0; i < int(imageSize); i += bytesPerPixel)	// Loop through the image data
			{
				swap(imageData[i], imageData[i + 2]);					// Swap the 1st and 3rd bytes ('r'ed and 'b'lue)
			}

			file.close();												// Close the file

			// Build a texture from the image data
			glGenTextures(1, &id);										// Generate OpenGL texture IDs

			glBindTexture(GL_TEXTURE_2D, id);							// Bind our texture

			if (!mipmap)
			{
				glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);	// Linear filtered
				glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	// Linear filtered
			}
			else
			{
				glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
				glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	// Linear filtered
			}

			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); // don't repeat texture
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); // don't repeat texture

			if (bpp == 24)												// Was the .tga image 24 bits?
				type = GL_RGB;											// If so, set the 'type' to GL_RGB

			if (!mipmap)
				gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height, type, GL_UNSIGNED_BYTE, imageData);
			else
				glTexImage2D(GL_TEXTURE_2D, 0, type, width, height, 0, type, GL_UNSIGNED_BYTE, imageData);
		}
		catch (char* error)
		{
			if (file.is_open())file.close();
			Logger->AddLog("Texture\t\t: LoadTGA(", false, true);
			*Logger << filename << ") - " << error << '\n';
			return false;
		}
		catch (...)
		{
			file.close();												// Close the file
			return false;												// Return false
		}

		return true;													// Texture building went OK, return true
	}

	bool Texture::LoadTGAc(char *filename, bool mipmap)
	{
		GLubyte		TGAheader[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 };	// Uncompressed TGA Header
		GLubyte		TGAcompare[12];								// Used To Compare TGA Header
		GLubyte		header[6];									// First 6 Useful Bytes From The Header
		GLuint		bytesPerPixel;								// Holds Number Of Bytes Per Pixel Used In The TGA File
		GLuint		imageSize;									// Used To Store The Image Size When Setting Aside Ram
		GLuint		type = GL_RGBA;								// Set The Default GL Mode To RBGA (32 BPP)
		FILE*		file;										// Our .tga file

		fopen_s(&file, filename, "rb");							// Open The TGA File

		if (file == NULL ||										// Does File Even Exist?
			fread(TGAcompare, 1, sizeof(TGAcompare), file) != sizeof(TGAcompare) ||	// Are There 12 Bytes To Read?
			memcmp(TGAheader, TGAcompare, sizeof(TGAheader)) != 0 ||	// Does The Header Match What We Want?
			fread(header, 1, sizeof(header), file) != sizeof(header))				// If So Read Next 6 Header Bytes
		{
			if (file == NULL)									// Did The File Even Exist? *Added Jim Strong*
				return false;									// Return False
			else
			{
				fclose(file);									// If Anything Failed, Close The File
				return false;									// Return False
			}
		}

		width = header[1] * 256 + header[0];			// Determine The TGA Width	(highbyte*256+lowbyte)
		height = header[3] * 256 + header[2];			// Determine The TGA Height	(highbyte*256+lowbyte)

		if (width <= 0 ||								// Is The Width Less Than Or Equal To Zero
			height <= 0 ||								// Is The Height Less Than Or Equal To Zero
			(header[4] != 24 && header[4] != 32))					// Is The TGA 24 or 32 Bit?
		{
			fclose(file);										// If Anything Failed, Close The File
			return false;										// Return False
		}

		bpp = header[4];							// Grab The TGA's Bits Per Pixel (24 or 32)
		bytesPerPixel = bpp / 8;						// Divide By 8 To Get The Bytes Per Pixel
		imageSize = width*height*bytesPerPixel;	// Calculate The Memory Required For The TGA Data
		if (bpp == 24)									// Was The TGA 24 Bits
		{
			type = GL_RGB;										// If So Set The 'type' To GL_RGB
		}
		imageData = (GLubyte *)malloc(imageSize);		// Reserve Memory To Hold The TGA Data

		if (imageData == NULL ||							// Does The Storage Memory Exist?
			fread(imageData, 1, imageSize, file) != imageSize)	// Does The Image Size Match The Memory Reserved?
		{
			if (imageData != NULL)						// Was Image Data Loaded
				free(imageData);						// If So, Release The Image Data

			fclose(file);										// Close The File
			return false;										// Return False
		}

		for (GLuint i = 0; i<int(imageSize); i += bytesPerPixel)		// Loop Through The Image Data
		{
			swap(imageData[i], imageData[i + 2]);		// Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue)
		}

		fclose(file);											// Close The File

		// Build A Texture From The Data
		glGenTextures(1, &id);					// Generate OpenGL texture IDs
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		glBindTexture(GL_TEXTURE_2D, id);			// Bind Our Texture

		if (!mipmap)
		{
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);	// Linear Filtered
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	// Linear Filtered
		}
		else
		{
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	// Linear Filtered
		}

		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); // don't repeat texture
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); // don't repeat texture
		if (!mipmap)
			gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height, type, GL_UNSIGNED_BYTE, imageData);
		else
			glTexImage2D(GL_TEXTURE_2D, 0, type, width, height, 0, type, GL_UNSIGNED_BYTE, imageData);

		return true;											// Texture Building Went Ok, Return True
	}

	bool Texture::LoadPNG(char *filename, bool mipmap)
	{
		vector<unsigned char> image;
		unsigned error = lodepng::decode(image, width, height, filename);

		// Error loading file
		if (error != 0) return false;

		imageData = new GLubyte[image.size()];

		// Copy the data over
		copy(image.begin(), image.end(), imageData);
		image.clear(); // clear the vector data

		glGenTextures(1, &id);
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		glBindTexture(GL_TEXTURE_2D, id);

		if (!mipmap)
		{
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		}
		else
		{
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		}

		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); // don't repeat texture
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); // don't repeat texture

		if (!mipmap)
			gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
		else
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);

		return true;
	}

	void Texture::Draw(vec3 v[], vec2 tc[], vec3 n)
	{
		if (v == NULL)
		{
			Logger->AddLog("Texture\t\t: Draw - Invalid vec3 pointer for parameter vertices");
			return;
		}

		float V[12], TC[8];

		if (tc == NULL) tc = const_cast<vec2*>(default_texcoordsv);

		for (int i = 0; i < 12; ++i)
		{
			V[i] = v[i / 4][i % 3];
			tc[i].asArray(&TC[i * 2]);
		}

		if (n == vec3(0, 0, 0))
			Draw(V, TC);
		else
		{
			float N[3]; n.asArray(N);
			Draw(V, TC, N);
		}
	}

	void Texture::Draw(float v[], float tc[], float n[])
	{
		if (v == NULL)
		{
			Logger->AddLog("Texture\t\t: Draw(float[], float[], float[]) - Invalid float pointer for parameter vertices");
			return;
		}

		if (tc == NULL) tc = const_cast<float*>(default_texcoords);

		glEnable(GL_TEXTURE_2D);

		glBindTexture(GL_TEXTURE_2D, id);

		glColor3f(1, 1, 1);

		glBegin(GL_TRIANGLES);

		// First Triangle //

		// top right //
		if (n != NULL) glNormal3fv(&n[0]);
		glTexCoord2fv(&tc[0]); glVertex3fv(&v[0]);

		// top left //
		if (n != NULL) glNormal3fv(&n[0]);
		glTexCoord2fv(&tc[2]); glVertex3fv(&v[3]);

		// bottom left //
		if (n != NULL) glNormal3fv(&n[0]);
		glTexCoord2fv(&tc[4]); glVertex3fv(&v[6]);

		// Second Triangle //

		// bottom left
		if (n != NULL) glNormal3fv(&n[0]);
		glTexCoord2fv(&tc[4]); glVertex3fv(&v[6]);

		// bottom right //
		if (n != NULL) glNormal3fv(&n[0]);
		glTexCoord2fv(&tc[6]); glVertex3fv(&v[9]);

		// top right //
		if (n != NULL) glNormal3fv(&n[0]);
		glTexCoord2fv(&tc[0]); glVertex3fv(&v[0]);

		glEnd();

		glDisable(GL_TEXTURE_2D);
	}

	Texture2D::Texture2D(const char *filename, vec2 size)
		: size(size)
	{
		this->LoadTGA(filename);
	}

	void Texture2D::LoadTGA(const char *filename)
	{
		Texture::LoadTGA((char*)filename, false);
	}

	void Texture2D::Draw(void)
	{
		float width = size.x / 2, height = size.y / 2;
		float verts[12] =
		{
			-width, -height, 0,
			-width, height, 0,
			width, height, 0,
			width, -height, 0
		};

		Texture::Draw(verts);
	}

	MultiTexture::MultiTexture(void)
	{
		InitMultiTexturing();
	}

	MultiTexture::~MultiTexture(void)
	{
	}

	void MultiTexture::drop(void)
	{
		for (uint i = 0; i < texArray.size(); ++i)
			texArray[i].drop();
	}

	Texture MultiTexture::getTexture(uint id)
	{
		if (id >= texArray.size() || id > 31)
			Logger->AddLog("MultiTexture\t\t: getTexture - Invalid ID");

		return texArray[id];
	}

	void MultiTexture::setTexture(uint id, Texture t)
	{
		if (id > 31)
			Logger->AddLog("MultiTexture\t\t: getTexture - Invalid ID");

		texArray.insert(texArray.begin() + id, t);
	}

	uint MultiTexture::addTexture(Texture t)
	{
		if (texArray.size() <= 31)
		{
			texArray.push_back(t);
			return texArray.size() - 1;
		}

		return -1;
	}

	bool MultiTexture::cutTexture(uint id)
	{
		if (id > texArray.size() || id > 31)
			return false;

		texArray.erase(texArray.begin() + id);

		return true;
	}

	bool MultiTexture::cutTexture(Texture t)
	{
		// Create a iterator for our vector
		vector<Texture>::iterator i;

		// Go through the vector till we find our target Texture
		for (i = texArray.begin(); i < texArray.end(); ++i)
		{
			if (i->id == t.id) break;
		}

		// If we don't find it, return false
		if (i == texArray.end())
			return false;

		// If we do find it, we'll take that Texture away,
		// std::vector will the the rest of the work
		texArray.erase(i);

		// Successful!
		return true;
	}

	void MultiTexture::Draw(vec3 v[], vec2 tc[], vec3 n)
	{
		if (v == NULL)
		{
			Logger->AddLog("MultiTexture\t\t: Draw - Invalid vec3 pointer for parameter vertices");
			return;
		}

		float V[12], TC[8];

		if (tc == NULL) tc = const_cast<vec2*>(default_texcoordsv);

		for (int i = 0; i < 12; ++i)
		{
			V[i] = v[i / 4][i % 3];
			tc[i].asArray(&TC[i * 2]);
		}

		if (n == vec3(0, 0, 0))
			Draw(V, TC);
		else
		{
			float N[3]; n.asArray(N);
			Draw(V, TC, N);
		}
	}

	void MultiTexture::Draw(float v[], float tc[], float n[])
	{
		if (v == NULL)
		{
			Logger->AddLog("MultiTexture\t\t: Draw(float[], float[], float[]) - Invalid float pointer for parameter vertices");
			return;
		}

		if (tc == NULL) tc = const_cast<float*>(default_texcoords);

		// Set Textures
		for (uint i = 0; i < texArray.size(); ++i)
		{
			glActiveTextureARB(GL_TEXTURE0_ARB + i);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, texArray[i].id);
		}

		glBegin(GL_TRIANGLES);

		// First Triangle //

		// top right //
		for (uint i = 0; i < texArray.size(); ++i)
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 1, 1);
		if (n != NULL) glNormal3fv(&n[0]);
		glVertex3fv(&v[0]);

		// top left //
		for (uint i = 0; i < texArray.size(); ++i)
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 0, 1);
		if (n != NULL) glNormal3fv(&n[0]);
		glVertex3fv(&v[3]);

		// bottom left //
		for (uint i = 0; i < texArray.size(); ++i)
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 0, 0);
		if (n != NULL) glNormal3fv(&n[0]);
		glVertex3fv(&v[6]);

		// Second Triangle //

		// bottom left //
		for (uint i = 0; i < texArray.size(); ++i)
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 0, 0);
		if (n != NULL) glNormal3fv(&n[0]);
		glVertex3fv(&v[6]);

		// bottom right //
		for (uint i = 0; i < texArray.size(); ++i)
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 1, 0);
		if (n != NULL) glNormal3fv(&n[0]);
		glVertex3fv(&v[9]);

		// top right //
		for (uint i = 0; i < texArray.size(); ++i)
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 1, 1);
		if (n != NULL) glNormal3fv(&n[0]);
		glVertex3fv(&v[0]);

		glEnd();

		for (uint i = 0; i < texArray.size(); ++i)
		{
			glActiveTextureARB(GL_TEXTURE0_ARB + i);
			glDisable(GL_TEXTURE_2D);
		}
	}

	bool InitMultiTexturing(void)
	{
		if (_glActiveTextureARB || _glMultiTexCoord2fARB) return true;

		try
		{
			// Load hardware supported extensions
			char *extension = (char*)glGetString(GL_EXTENSIONS);

			// Check multitexture support
			if (strstr(extension, "GL_ARB_multitexture") == 0)
				cout << "Error : Multitexturing not supported!" << endl;

			// Load multitexture function
			_glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
			_glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
			if (!_glActiveTextureARB || !_glMultiTexCoord2fARB) // check functions are loaded
				cout << "Error : Loading multitextures functions!" << endl;
		}
		catch (char* error)
		{
			Logger->AddLog("MultiTexture\t\t: InitMultiTexturing - ", false, true);
			*Logger << error << "\n";
			return false;
		}
		catch (...)
		{
			return false;
		}

		return true;
	}

	void glActiveTextureARB(GLenum texture)
	{
		if (!_glActiveTextureARB && !InitMultiTexturing()) return;

		_glActiveTextureARB(texture);
	}

	void glMultiTexCoord2fARB(GLenum target, GLfloat s, GLfloat t)
	{
		if (!_glMultiTexCoord2fARB && !InitMultiTexturing()) return;

		_glMultiTexCoord2fARB(target, s, t);
	}

} // namespace GeorgeLib